#include "metaballs.h"
#include "game.h"
#include <QTime>



// for readability, the screen and texture sizes are hard-coded
const float SCREEN_WIDTH=640.0f;
const float SCREEN_HEIGHT=360.0f;


VGPath path;
VGPaint paint;

    // images
VGuint bgImage;
VGuint centerImage;
VGuint capsuleImage;

// temp buffer for transformed path coordinates
VGubyte moveTo=VG_MOVE_TO_ABS;
VGubyte lineTo=VG_LINE_TO_ABS;
VGubyte closePath=VG_CLOSE_PATH;

/** walltime */
float t;
int frames=0;

#define BALL_COUNT 16
Ball *balls = 0;
MetaballSystem *mbs;

VGPath blobs[BALL_COUNT];


QVector2D capsulePosition;          // where is the medical capsule laying
float capsulePower;




/**
  * Loads a PNG file and creates VGImage using the 32bit image data.
  */
VGImage loadVGImage(QString filename)
{
    QImage texture;
    if(!texture.load(filename))
    {
        qDebug("loadVGImage cannot load '%s'", filename);
        return VG_INVALID_HANDLE;
    }

    int w=texture.width();
    int h=texture.height();
    if (w<1 || h<1) return VG_INVALID_HANDLE;

    VGImage image = vgCreateImage(VG_sARGB_8888, w, h, VG_IMAGE_QUALITY_FASTER);
    if(image==VG_INVALID_HANDLE)
    {
        VGErrorCode err = vgGetError();
        qDebug("loadVGImage error 0x%x", err);
    }

    vgImageSubData(image, (QRgb*)texture.bits(), w*4, VG_sARGB_8888, 0, 0, w, h);
    return image;
}




/**
 *
 * Initialize the game. This is called once when the application is starting up.
 *
 */
void game_init()
{
    capsulePower = -1.0f;                // capsule is not on the table

        // Create the balls
    balls = new Ball[ BALL_COUNT ];

        // Initialize the balls
    for (int f=0; f<BALL_COUNT; f++) {
        balls[f].pos = QVector2D( (rand() & 255) * SCREEN_WIDTH / 255.0f,(rand() & 255) * SCREEN_HEIGHT / 255.0f);
        balls[f].size =  2+(rand() & 3);
    }

        // Create metaballsystem to control the balls.
    mbs = new MetaballSystem( BALL_COUNT, balls,  2.0, 4*0.0015);
}


/**
 *
 * Destroy everything. Will be called when the application is about to exit.
 *
 */
void game_destroy()
{
    delete mbs; mbs = 0;
    delete [] balls; balls = 0;
}


/**
 *
 * Called after init. And everytime we are coming back from background.
 *
 */
void game_prepare()
{
    vgSeti(VG_FILL_RULE, VG_NON_ZERO);

    bgImage  = loadVGImage(":/images/bg.png");
    centerImage  = loadVGImage(":/images/center.png");
    capsuleImage  = loadVGImage(":/images/capsule.png");
    paint = vgCreatePaint();

    vgSetParameteri(paint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
    vgSetColor(paint, 0xff00ffff);

    path=vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
    vguRect(path, 0, 0, 4, 4);

    for(int i=0;i<mbs->ballCount;i++)
    {
        blobs[i]=vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
    }

}


/**
 *
 * Called before destroy. And everytime we are going to background.
 *
 */
void game_release()
{
   vgDestroyImage( bgImage ); bgImage = VG_INVALID_HANDLE;
   vgDestroyImage( centerImage ); centerImage = VG_INVALID_HANDLE;
   vgDestroyImage( capsuleImage ); capsuleImage = VG_INVALID_HANDLE;


    vgDestroyPath(path);
    vgDestroyPaint(paint);
    for(int i=0;i<mbs->ballCount;i++)
    {
        vgDestroyPath(blobs[i]);
    }
}


/**
 *
 * Update. Walltime is the seconds we have been running
 *
 */
void game_update(float walltime)
{
    float frameTime = walltime-t;
    if (frameTime>0.1f) frameTime = 0.1f;
    t=walltime;

    QVector2D expos,temp;

    // Update the capsule
    if (capsulePower>=0.0f) {

        for (int f=0; f<BALL_COUNT; f++) {
            temp = balls[f].pos - capsulePosition;
            float invpower = QVector2D::dotProduct(temp, temp)*0.3f;
            invpower = (10000.0f / (invpower+1.0f)) * frameTime * capsulePower;
            if (invpower>1.0f) invpower = 1.0f;

            if (balls[f].size>1.0f) {
                balls[f].size -= (balls[f].size-1.0f) * invpower * 0.1f;
            }
            //if (invpower>10.0f) balls[f].size -= 2;
            //if (balls[f].size<1.0f) balls[f].size = 1.0f;
            balls[f].dir += temp*invpower;
        }
    }



    // Update the balls.
    for (int f=0; f<mbs->ballCount; f++) {
        Ball &b = mbs->balls[f];

            // gravity to other balls.
        for (int g=0; g<mbs->ballCount; g++)
            if (f!=g) {
                Ball &b2 = mbs->balls[g];
                temp = b2.pos - b.pos;

                float invpower = QVector2D::dotProduct(temp,temp)*0.05f;
                //if (invpower<2.0f) invpower = 2.0f;

                temp *= (2000.0f / (invpower*invpower+1.0f))*frameTime;
                b.dir += temp;
                b2.dir -= temp;
            }

            // Some random movement
        b.dir += QVector2D( (float)(rand() & 255)/255.0f-0.5f, (float)(rand() & 255)/255.0f-0.5f ) * frameTime * 500.0f;
        b.size += (10.0f - b.size) * frameTime * 0.05f;

        expos = b.pos;
        temp = (b.dir * frameTime);
        b.pos += temp;
        b.dir -= temp;


        // bound checking
        if (b.pos.x() < 0 || b.pos.x() > SCREEN_WIDTH) {b.dir.setX( -b.dir.x() ); b.pos = expos; }
        if (b.pos.y() < 0 || b.pos.y() > SCREEN_HEIGHT) {b.dir.setY( -b.dir.y() ); b.pos = expos; }

    }



}

void debug_point(float x, float y, int color)
{
    vgLoadIdentity();
    vgTranslate(x, y);
    vgSetColor(paint, color);
    vgSetPaint(paint, VG_FILL_PATH | VG_STROKE_PATH);
    vgDrawPath(path, VG_FILL_PATH | VG_STROKE_PATH);
    vgLoadIdentity();
}



void draw_balls()
{
    float step = 10;

    /*
    # First, track the border for all balls and store
    # it to pos0 and edgePos. The latter will move along the border,
    # pos0 stays at the initial coordinates.
     */
    for(int i=0;i<mbs->ballCount;i++)
    {
        Ball &b = mbs->balls[i];
        //b.pos = QVector2D(sin(t*.2+i*7.1)*200+320,cos(i*4.1+t*.3)*150+180);

        QVector2D p=b.pos;
        b.pos0 = mbs->trackTheBorder(b.pos+QVector2D(0,1));
        b.edgePos = b.pos0;
        b.tracking = true;
        b.sharedBall=-1;
        b.verticeCount=0;

        vgClearPath(blobs[i], VG_PATH_CAPABILITY_APPEND_TO);

        p=b.pos0;
        float xy[2]={p.x(), SCREEN_HEIGHT-p.y()};
#if 0
        debug_point(xy[0], xy[1], 0xff0000ff);
#endif
//        vgAppendPathData(blobs[i], 1, &moveTo, xy);

        b.commands[b.verticeCount]=VG_MOVE_TO_ABS;
        b.vertices[b.verticeCount*2]=xy[0];
        b.vertices[b.verticeCount*2+1]=xy[1];
        b.verticeCount++;
    }



    for(int i=0;i<mbs->ballCount;i++)
    {
        for(int c=0;c<100;c++)
        {
            Ball &ball = mbs->balls[i];
            if(!ball.tracking)
            {
                continue;
            }

            //# walk along the tangent, using chosen differential method
            ball.edgePos = mbs->rungeKutta2Tangent(ball.edgePos, step);

            //# correction step towards the border
            mbs->stepOnceTowardsBorder(ball.edgePos);

            //# check if we've gone a full circle or hit some other
            //# edge tracker

            int otherball=-1;
            for(int j=0;j<mbs->ballCount;j++)
            {
                Ball &ob = mbs->balls[j];
                if((i!=j || c>3) && (ob.pos0-ball.edgePos).lengthSquared()<step*step)
                {
                    ball.tracking=false;
                    otherball=j;
                }
            }

            QVector2D p=ball.edgePos;
            float xy[2]={p.x(), SCREEN_HEIGHT-p.y()};


            ball.commands[ball.verticeCount]=VG_LINE_TO_ABS;
            ball.vertices[ball.verticeCount*2]=xy[0];
            ball.vertices[ball.verticeCount*2+1]=xy[1];
            ball.verticeCount++;

            if(otherball!=-1)
            {
                QVector2D p=balls[otherball].pos0;
                float xy[2]={p.x(), SCREEN_HEIGHT-p.y()};
                ball.commands[ball.verticeCount]=VG_LINE_TO_ABS;
                ball.vertices[ball.verticeCount*2]=xy[0];
                ball.vertices[ball.verticeCount*2+1]=xy[1];
                ball.verticeCount++;
            }
        }

        vgLoadIdentity();
        vgAppendPathData(blobs[i], mbs->balls[i].verticeCount, mbs->balls[i].commands, mbs->balls[i].vertices);

        //vgSetColor(paint, 0xFFFFFF7F);
        vgSetColor(paint, 0x0000007F);
        vgSetPaint(paint, VG_FILL_PATH | VG_STROKE_PATH);
        vgDrawPath(blobs[i], 0*VG_FILL_PATH | VG_STROKE_PATH);
    }
}


/**
 *
 * Render everything.
 *
 */
void game_render()
{
    // Draw the background
    vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
    vgLoadIdentity();
    vgScale(SCREEN_WIDTH/512.0f, SCREEN_HEIGHT/512.0f );
    vgDrawImage( bgImage );



        // Draw the centers.
    vgScale(1,1);
    for(int i=0;i<mbs->ballCount;i++)
    {
        vgLoadIdentity();
        QVector2D p=mbs->balls[i].pos;
        float scale = 0.3f + mbs->balls[i].size * 0.15f;
        vgTranslate(p.x()-64 * scale, SCREEN_HEIGHT-p.y()-64 * scale);
        vgScale(scale,scale );
        vgDrawImage( centerImage );
    }


    // Render the capsule
    if (capsulePower>=0.0f) {
        vgScale(1,1);
        vgLoadIdentity();
        float scale = -0.6f;
        vgTranslate(capsulePosition.x()-64 * scale, SCREEN_HEIGHT-capsulePosition.y()-64 * scale);
        vgScale(scale,scale );
        vgDrawImage( capsuleImage );
    }



    // center to screen
    vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
    frames++;


    vgSetPaint(paint, VG_STROKE_PATH);
    vgSeti(VG_STROKE_CAP_STYLE, VG_CAP_BUTT);
    vgSetf(VG_STROKE_LINE_WIDTH, 8);

    draw_balls();
}


void game_event(int type, float x, float y, float z)
{
    capsulePosition = QVector2D( x,y );
    switch (type) {
        defualt:
        case Qt::TouchPointPressed:
            capsulePower = 1.0f;
            break;
        case Qt::TouchPointReleased:
            capsulePower = -1.0f;
    }
}



